/*
 * (C) Copyright 2001-2008
 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 * Keith Outwater, keith_outwater@mvis.com`
 * Copyright (C) 2022 - 2023, Phytium Technology Co., Ltd. All rights reserved.<BR>
 *
 * SPDX-License-Identifier: GPL-2.0+
 */

/*
 * Date & Time support (no alarms) for Dallas Semiconductor (now Maxim)
 * DS1337 Real Time Clock (RTC).
 */

#include <GetSetRtcDxe.h>
#include <Protocol/PowerManageDxe.h>
#include <Uefi.h>
#include <Library/UefiRuntimeServicesTableLib.h>

#if defined(CONFIG_CMD_DATE)

#if 1
void i2c_reg_write(uchar addr, uchar reg, uchar val);
uchar i2c_reg_read(uchar addr, uchar reg);
#endif

/*
 * RTC register addresses
 */
#if defined CONFIG_RTC_DS1337
#define RTC_SEC_REG_ADDR    0x0
#define RTC_MIN_REG_ADDR    0x1
#define RTC_HR_REG_ADDR     0x2
#define RTC_DAY_REG_ADDR    0x3
#define RTC_DATE_REG_ADDR   0x4
#define RTC_MON_REG_ADDR    0x5
#define RTC_YR_REG_ADDR     0x6
//[gliu-0009]
#define RTC_A1M1_SECONDS_REG 0x7
#define RTC_A1M2_MINUTES_REG 0x8
#define RTC_A1M3_HOURS_REG   0x9
//[gliu-0024]add-start
#define RTC_A1M4_DAYS_REG    0xa
#define RTC_A2M2_MINUTES_REG  0xb
#define RTC_A2M3_HOURS_REG    0xc
#define RTC_A2M4_DAYS_REG    0xd
//[gliu-0024]add-end
//[gliu-0009]
#define RTC_CTL_REG_ADDR    0x0e
#define RTC_STAT_REG_ADDR   0x0f
#define RTC_TC_REG_ADDR     0x10
#elif defined CONFIG_RTC_DS1388
#define RTC_SEC_REG_ADDR    0x1
#define RTC_MIN_REG_ADDR    0x2
#define RTC_HR_REG_ADDR     0x3
#define RTC_DAY_REG_ADDR    0x4
#define RTC_DATE_REG_ADDR   0x5
#define RTC_MON_REG_ADDR    0x6
#define RTC_YR_REG_ADDR     0x7
#define RTC_CTL_REG_ADDR    0x0c
#define RTC_STAT_REG_ADDR   0x0b
#define RTC_TC_REG_ADDR     0x0a
#endif

/*
 * RTC control register bits
 */
#define RTC_CTL_BIT_A1IE    0x1 /* Alarm 1 interrupt enable */
#define RTC_CTL_BIT_A2IE    0x2 /* Alarm 2 interrupt enable */
#define RTC_CTL_BIT_INTCN   0x4 /* Interrupt control        */
#define RTC_CTL_BIT_RS1     0x8 /* Rate select 1        */
#define RTC_CTL_BIT_RS2     0x10    /* Rate select 2        */
#define RTC_CTL_BIT_DOSC    0x80    /* Disable Oscillator       */

/*
 * RTC status register bits
 */
#define RTC_STAT_BIT_A1F    0x1 /* Alarm 1 flag         */
#define RTC_STAT_BIT_A2F    0x2 /* Alarm 2 flag         */
#define RTC_STAT_BIT_OSF    0x80    /* Oscillator stop flag     */

//[gliu-0024]add-start
/*
 * RTC A1M4_DAYS register bits
 */
#define RTC_A1M4_DAYS_BIT_DYDT  (1<<6)  /* The DY/DT bits   */
#define RTC_A1M4_DAYS_BIT_A1M4  (1<<7)  /* The A1M4 bits    */
#define RTC_A2M4_DAYS_BIT_DYDT  (1<<6)  /* The DY/DT bits   */
#define RTC_A2M4_DAYS_BIT_A2M4  (1<<7)  /* The A2M4 bits    */

//[gliu-0024]add-end

//[wyj-0003]add-start
EFI_GUID  gEfiRTCEnabledGuid = { 0x2942755f, 0x4652, 0x4e54, { 0xb2, 0x77, 0xc0, 0x15, 0x4c, 0x97, 0xcf, 0x83 }};
//[wyj-0003]add-end

//[gliu-0010]
extern UINTN                  mRtcBase;
//[gliu-0010]
STATIC uchar rtc_read (uchar reg,UINTN slave_addr);
STATIC void rtc_write (uchar reg, uchar val,UINTN slave_addr);


unsigned int
bcd2bin (
  UINT8 val
  )
{
    return ((val) & 0x0f) + ((val) >> 4) * 10;
}

UINT8
bin2bcd (
  unsigned int val
  )
{
    return (((val / 10) << 4) | (val % 10));
}

/*
 * Utility routines to read/write registers.
 */
u8 i2c_reg_read(u8 addr, u8 reg)
{
    u8 buf;
//[gliu-0010]
    //UINT64 I2CBaseAddress = PcdGet64 (PcdRtcI2cControllerBaseAddress);
    //DEBUG((DEBUG_ERROR, " %a %a line %d:\n",__FILE__,__func__,__LINE__));
    //i2c_read(I2CBaseAddress, addr, reg, 1, &buf, 1);
    i2c_read(mRtcBase, addr, reg, 1, &buf, 1);
//[gliu-0010]
    //DEBUG((DEBUG_ERROR, " buff is %x:\n",buf));

    return buf;
}

void i2c_reg_write(u8 addr, u8 reg, u8 val)
{
//[gliu-0010]
    //UINT64 I2CBaseAddress = PcdGet64 (PcdRtcI2cControllerBaseAddress);
    //i2c_write(I2CBaseAddress, addr, reg, 1, &val, 1);
    i2c_write(mRtcBase, addr, reg, 1, &val, 1);
//[gliu-0010]
}

uchar rtc_read (uchar reg,UINTN slave_addr)
{
    //DEBUG((DEBUG_ERROR, " %a %a line %d:\n",__FILE__,__func__,__LINE__));
    return (i2c_reg_read (slave_addr, reg));
}


void rtc_write (uchar reg, uchar val,UINTN slave_addr)
{
    i2c_reg_write (slave_addr, reg, val);
}


/*
 * Get the current time from the RTC
 */
int rtc_get (rtc_time *tmp,UINTN slave_addr)
{
    uchar sec, min, hour, mday, mon_cent, year;//, status;
    //DEBUG((DEBUG_ERROR, " %a %a line %d:\n",__FILE__,__func__,__LINE__));

  // status = rtc_read (RTC_STAT_REG_ADDR);
    sec = rtc_read (RTC_SEC_REG_ADDR, slave_addr);
    min = rtc_read (RTC_MIN_REG_ADDR, slave_addr);
    hour = rtc_read (RTC_HR_REG_ADDR, slave_addr);
    mday = rtc_read (RTC_DATE_REG_ADDR, slave_addr);
    mon_cent = rtc_read (RTC_MON_REG_ADDR, slave_addr);
    year = rtc_read (RTC_YR_REG_ADDR, slave_addr);


    // if (status & RTC_STAT_BIT_OSF) {
    //  //printf ("### Warning: RTC oscillator has stopped\n");
    //  /* clear the OSF flag */
    //  rtc_write (RTC_STAT_REG_ADDR,
    //         rtc_read (RTC_STAT_REG_ADDR) & ~RTC_STAT_BIT_OSF);
    //  // This will make setup unhappy.  rel = -1;
    // }

    tmp->tm_sec  = bcd2bin (sec & 0x7F);

    //DEBUG((DEBUG_ERROR, " tm_sec is %x:\n",tmp->tm_sec));
    tmp->tm_min  = bcd2bin (min & 0x7F);

    //DEBUG((DEBUG_ERROR, " tm_min is %x:\n",tmp->tm_min));
    tmp->tm_hour = bcd2bin (hour & 0x3F);

    //DEBUG((DEBUG_ERROR, " tm_hour is %x:\n",tmp->tm_hour));
    tmp->tm_mday = bcd2bin (mday & 0x3F);

    //DEBUG((DEBUG_ERROR, " tm_mday is %x:\n",tmp->tm_mday));
    tmp->tm_mon  = bcd2bin (mon_cent & 0x1F);

    //DEBUG((DEBUG_ERROR, " tm_mon is %x:\n",tmp->tm_mon));
    tmp->tm_year = bcd2bin (year) + 2000;
    // tmp->tm_wday = bcd2bin ((wday - 1) & 0x07);
    tmp->tm_yday = 0;
    tmp->tm_isdst= 0;

    return 0;
}


/*
 * Set the RTC
 */
int rtc_set (EFI_TIME *tmp, rtc_time *current,UINTN slave_addr)
{

  if (tmp->Year != current->tm_year) {
    rtc_write (RTC_YR_REG_ADDR, bin2bcd (tmp->Year % 100), slave_addr);
  }

  if (tmp->Month != current->tm_mon) {
    //[gliu-0013]add-start
    //rtc_write (RTC_MON_REG_ADDR, bin2bcd (tmp->Month) | 0x80, slave_addr);
    rtc_write (RTC_MON_REG_ADDR, bin2bcd (tmp->Month), slave_addr);
    //[gliu-0013]add-end
  }

  if (tmp->Day != current->tm_mday) {
    rtc_write (RTC_DATE_REG_ADDR, bin2bcd (tmp->Day), slave_addr);
  }
  if (tmp->Hour != current->tm_hour) {
    rtc_write (RTC_HR_REG_ADDR, bin2bcd (tmp->Hour), slave_addr);
  }
  if (tmp->Minute != current->tm_min) {
    rtc_write (RTC_MIN_REG_ADDR, bin2bcd (tmp->Minute), slave_addr);
  }
  if (tmp->Second != current->tm_sec)
    rtc_write (RTC_SEC_REG_ADDR, bin2bcd (tmp->Second), slave_addr);

  return 0;
}

//[gliu-0009]
EFI_STATUS
GetWakeUp (
  OUT BOOLEAN      *Enabled,
  OUT BOOLEAN      *Pending,
  OUT EFI_TIME     *tmp,
  IN UINTN          slave_addr
  )
{
  //[gliu-0024]add-start
  UINT8 Date = 0;
  UINT8 Minute = 0;
  UINT8 Hour = 0;
  //[gliu-0024]add-end
  //[wyj-0001]add-start
  UINT8 Second = 0;
  uchar month, year;
  //[wyj-0001]add-end

  if (tmp == NULL || Enabled == NULL || Pending == NULL) {
    return EFI_INVALID_PARAMETER;
  }
//[gliu-0037]add-start
#if (defined HWQS)
  *Enabled = rtc_read (RTC_CTL_REG_ADDR, slave_addr) & RTC_CTL_BIT_A2IE;
  *Pending = rtc_read (RTC_STAT_REG_ADDR, slave_addr) & RTC_STAT_BIT_A2F;
  Date = rtc_read (RTC_A2M4_DAYS_REG, slave_addr);
  Minute = rtc_read (RTC_A2M2_MINUTES_REG, slave_addr);
  Hour   = rtc_read (RTC_A2M3_HOURS_REG, slave_addr);
  tmp->Day  = bcd2bin (Date & 0x7F);
  tmp->Minute  = bcd2bin (Minute & 0x7F);
  tmp->Hour    = bcd2bin (Hour & 0x3F);

  //[wyj-0001]add-start
  tmp->Second = bcd2bin (Second & 0x7F);

  month = rtc_read (RTC_MON_REG_ADDR, slave_addr);
  year = rtc_read (RTC_YR_REG_ADDR, slave_addr);

  tmp->Month = bcd2bin (month & 0x1F);
  tmp->Year = bcd2bin (year) + 2000;

  tmp->Nanosecond = 0;
  //[wyj-0001]add-end
#else
  if (slave_addr == CONFIG_RTC_DS1337) {
    *Enabled = rtc_read (RTC_CTL_REG_ADDR, slave_addr) & RTC_CTL_BIT_A1IE;
    *Pending = rtc_read (RTC_STAT_REG_ADDR, slave_addr) & RTC_STAT_BIT_A1F;
    //[wyj-0003]add-start
    if (!EfiAtRuntime())
    {
      UINTN           DataSize;
      BOOLEAN         Rtcenable = 0;

      DataSize = sizeof(BOOLEAN);

      gRT->GetVariable(
                L"Rtcenable",
                &gEfiRTCEnabledGuid,
                NULL,
                &DataSize,
                &Rtcenable
                );

      if ( Rtcenable != 1 )
      {
      *Enabled = rtc_read (RTC_CTL_REG_ADDR, slave_addr) & RTC_CTL_BIT_A2IE;
      }
    }
    //[wyj-0003]add-end

  } else if (slave_addr == CONFIG_RTC_SD3068) {
     *Enabled = rtc_read (0x10, slave_addr) & 0x2;
     *Pending = rtc_read (0x0F, slave_addr) & 0x20;
  }
  Date = rtc_read (RTC_A1M4_DAYS_REG, slave_addr);
  Minute = rtc_read (RTC_A1M2_MINUTES_REG, slave_addr);
  Hour   = rtc_read (RTC_A1M3_HOURS_REG, slave_addr);
  tmp->Day  = bcd2bin (Date & 0x7F);
  tmp->Minute  = bcd2bin (Minute & 0x7F);
  tmp->Hour    = bcd2bin (Hour & 0x3F);

  //[wyj-0001]add-start
  Second = rtc_read (RTC_A1M1_SECONDS_REG,slave_addr);
  tmp->Second = bcd2bin (Second & 0x7F);

  month = rtc_read (RTC_MON_REG_ADDR, slave_addr);
  year = rtc_read (RTC_YR_REG_ADDR, slave_addr);

  tmp->Month = bcd2bin (month & 0x1F);
  tmp->Year = bcd2bin (year) + 2000;

  tmp->Nanosecond = 0;
  //[wyj-0001]add-end

#endif
//[gliu-0037]add-end
  return EFI_SUCCESS;
}

EFI_STATUS
SetWakeUp (
  IN BOOLEAN        Enabled,
  OUT EFI_TIME     *tmp,
  IN UINTN          slave_addr
  )
{
  UINT8 Temp;
  UINT8 Date = 0;
  Temp = 0;

  //[wyj-0003]add-start
  if (!EfiAtRuntime())
  {
    UINTN           DataSize;
    BOOLEAN         Rtcenable = 0;

    Rtcenable = Enabled;
    DataSize = sizeof(BOOLEAN);

    gRT->SetVariable (
                  L"Rtcenable",
                  &gEfiRTCEnabledGuid,
                  EFI_VARIABLE_NON_VOLATILE|
                  EFI_VARIABLE_BOOTSERVICE_ACCESS|
                  EFI_VARIABLE_RUNTIME_ACCESS,
                  DataSize,
                  &Rtcenable
                  );
  }
  //[wyj-0003]add-end

//[gliu-0037]add-start
#if (defined HWQS)
    //disable alarm
    if (!Enabled){
      Temp = rtc_read(RTC_CTL_REG_ADDR, slave_addr);
      Temp &= ~(1 << 0);
      rtc_write (RTC_CTL_REG_ADDR, Temp, slave_addr);
      return EFI_SUCCESS;
    }
    if((tmp->Day & RTC_EVERY_WEEK_FLAG) != 0)
    {
      Date = bin2bcd((tmp->Day)&0x3f) | RTC_A2M4_DAYS_BIT_DYDT;
    }else{
      Date = bin2bcd((tmp->Day)&0x3f);
    }
    tmp->Day = (tmp->Day) & 0x3f;
    if (tmp == NULL || !IsTimeValid(tmp)) {
      return EFI_INVALID_PARAMETER;
    }

    //enable alarm
    rtc_write (RTC_STAT_REG_ADDR, 0,   slave_addr);
    rtc_write (RTC_CTL_REG_ADDR, RTC_CTL_BIT_A2IE|RTC_CTL_BIT_INTCN,   slave_addr);
    //set alarm time
    rtc_write (RTC_A2M2_MINUTES_REG, bin2bcd (tmp->Minute), slave_addr);
    rtc_write (RTC_A2M3_HOURS_REG,   bin2bcd (tmp->Hour), slave_addr);
    rtc_write (RTC_A2M4_DAYS_REG, Date, slave_addr);
#else
  if (slave_addr == CONFIG_RTC_DS1337 ){

    //disable alarm
    if (!Enabled){
      Temp = rtc_read(RTC_CTL_REG_ADDR, slave_addr);
      Temp &= ~(1 << 0);
      rtc_write (RTC_CTL_REG_ADDR, Temp, slave_addr);
      return EFI_SUCCESS;
    }
    //[gliu-0024]add-start
    if((tmp->Day & RTC_EVERY_WEEK_FLAG) != 0)
    {
      Date = bin2bcd((tmp->Day)&0x3f) | RTC_A1M4_DAYS_BIT_DYDT;
    }else if ((tmp->Day & RTC_EVERY_DAYS_FLAG) != 0) {
      Date = bin2bcd((tmp->Day)&0x3f) | RTC_A1M4_DAYS_BIT_A1M4;
    }else{
      Date = bin2bcd((tmp->Day)&0x3f);
    }
    tmp->Day = (tmp->Day) & 0x3f;
    //[gliu-0024]add-end
    if (tmp == NULL || !IsTimeValid(tmp)) {
      return EFI_INVALID_PARAMETER;
    }

    //enable alarm
    rtc_write (RTC_STAT_REG_ADDR, 0,   slave_addr);
    //[gliu-0024]add-start
    rtc_write (RTC_CTL_REG_ADDR, RTC_CTL_BIT_A1IE|RTC_CTL_BIT_INTCN,   slave_addr);
    //[gliu-0024]add-end
    //set alarm time
    rtc_write (RTC_A1M1_SECONDS_REG, bin2bcd (tmp->Second), slave_addr);
    rtc_write (RTC_A1M2_MINUTES_REG, bin2bcd (tmp->Minute), slave_addr);
    rtc_write (RTC_A1M3_HOURS_REG,   bin2bcd (tmp->Hour), slave_addr);
    //[gliu-0024]add-start
    rtc_write (RTC_A1M4_DAYS_REG, Date, slave_addr);
    //[gliu-0024]add-end
  } else if (slave_addr == CONFIG_RTC_SD3068){

      //disable alarm
      if (!Enabled){
        rtc_write (RTC_CTL_REG_ADDR, 0x0, slave_addr);
        Temp = rtc_read(RTC_TC_REG_ADDR, slave_addr);
        Temp &= ~(1 << 1);
        rtc_write (RTC_TC_REG_ADDR, Temp, slave_addr);
        return EFI_SUCCESS;
      }

      if (tmp == NULL || !IsTimeValid(tmp)) {
        return EFI_INVALID_PARAMETER;
      }

      rtc_write (RTC_TC_REG_ADDR, 0x92, slave_addr);        //10h    1001 0010 IM=0 INTS1=0 INTS0=1 INTAE=1
      rtc_write (RTC_CTL_REG_ADDR, 0x7, slave_addr);        //enable alarm
      rtc_write (RTC_STAT_REG_ADDR, 0xFF, slave_addr);
      rtc_write (RTC_A1M1_SECONDS_REG, bin2bcd (tmp->Second), slave_addr);
      rtc_write (RTC_A1M2_MINUTES_REG, bin2bcd (tmp->Minute), slave_addr);
      rtc_write (RTC_A1M3_HOURS_REG,   bin2bcd (tmp->Hour), slave_addr);
  }
#endif
//[gliu-0037]add-end
  return EFI_SUCCESS;
}
//[gliu-0009]

/*
 * Reset the RTC.  We also enable the oscillator output on the
 * SQW/INTB* pin and program it for 32,768 Hz output. Note that
 * according to the datasheet, turning on the square wave output
 * increases the current drain on the backup battery from about
 * 600 nA to 2uA. Define CONFIG_SYS_RTC_DS1337_NOOSC if you wish to turn
 * off the OSC output.
 */

#ifdef CONFIG_SYS_RTC_DS1337_NOOSC
 #define RTC_DS1337_RESET_VAL \
    (RTC_CTL_BIT_INTCN | RTC_CTL_BIT_RS1 | RTC_CTL_BIT_RS2)
#else
 #define RTC_DS1337_RESET_VAL (RTC_CTL_BIT_RS1 | RTC_CTL_BIT_RS2)
#endif
void rtc_reset (void)
{
#ifdef CONFIG_SYS_RTC_DS1337
    rtc_write (RTC_CTL_REG_ADDR, RTC_DS1337_RESET_VAL);
#elif defined CONFIG_SYS_RTC_DS1388
    rtc_write(RTC_CTL_REG_ADDR, 0x0); /* hw default */
#endif
#ifdef CONFIG_SYS_DS1339_TCR_VAL
    rtc_write (RTC_TC_REG_ADDR, CONFIG_SYS_DS1339_TCR_VAL);
#endif
#ifdef CONFIG_SYS_DS1388_TCR_VAL
    rtc_write(RTC_TC_REG_ADDR, CONFIG_SYS_DS1388_TCR_VAL);
#endif
}

#endif
